package map;

import java.util.Random;
import com.jme.bounding.BoundingBox;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.Point;
import com.jme.scene.Spatial;
import com.jme.scene.state.*;
import com.jme.system.DisplaySystem;
import java.nio.FloatBuffer;

/**
 * StarDust
 *
 * @author Zen, Matthew D. Hicks, thzero, ractoc, Methius
 */
public class Stardust extends Node {
	
    private static final long serialVersionUID = 1L;
    
    private static final float MAX_VIEW_DIST = 400f;
    
    private int blockSize;
    private Point[][][] points;
    private int numStars;
    private int oldSecX;
    private int oldSecY;
    private int oldSecZ;
    
    public Stardust(int numStars, int blockSize, Camera cam) {
        super("Stardust");
        this.blockSize = blockSize;
        this.numStars = numStars;
        
        points = new Point[3][3][3];
        setIsCollidable(false);
        // A star field
        //
        // in this first edition, just use the standard 'point' class
        // but in future would like to have a custom drawn one - where intensity
        // is related to distance?
        Random r = new Random();
        Vector3f[] vertexes = new Vector3f[numStars];
        ColorRGBA[] colors = new ColorRGBA[numStars];
        for (int x = 0; x < numStars; ++x) {
            vertexes[x] = new Vector3f((r.nextFloat()) * blockSize, (r.nextFloat()) * blockSize, (r.nextFloat()) * blockSize);
            colors[x] = ColorRGBA.black.clone();
        //colors[x].a = .4f + .6f * r.nextFloat();// .5f + .5f *
        // r.nextFloat();
        }
        // all dust particles are white
        MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
        float alpha = 1.0f; //.5f + r.nextFloat()*.5f;
        ms.setEmissive(new ColorRGBA(0.0f, 0.0f, 1.0f, alpha));
        ms.setDiffuse(new ColorRGBA(0.0f, 0.0f, 1.0f, alpha));
        ms.setEnabled(true);
        BlendState as = DisplaySystem.getDisplaySystem().getRenderer().createBlendState();
        as.setBlendEnabled(true);
        as.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
        as.setDestinationFunction(BlendState.DestinationFunction.OneMinusSourceAlpha);
        as.setTestEnabled(false);
        as.setEnabled(true);
        for (int k = 0; k < 3; ++k) {
            for (int j = 0; j < 3; ++j) {
                for (int i = 0; i < 3; ++i) {
                    Node sector = new Node("sector" + i + j + k);
                    points[i][j][k] = new Point("stardust " + i + "" + j + "" + k, vertexes, null, colors, null);
                    if(true) {
                    //points[i][j][k].setPointSize(1.0f+(float)Math.random() * .7f);
                       points[i][j][k].setPointSize(1.0f);
                    }
                    points[i][j][k].setLocalTranslation(new Vector3f((i - 1) * blockSize, (j - 1) * blockSize, (k - 1) * blockSize));
                    points[i][j][k].setModelBound(new BoundingBox());
                    points[i][j][k].updateModelBound();
                    points[i][j][k].setCastsShadows(false);
                    sector.attachChild(points[i][j][k]);
                    sector.setModelBound(new BoundingBox());
                    sector.updateModelBound();
                    attachChild(sector);
                }
            }
        }
        // We don't want the light to affect our dust
        updateWorldBound();
        this.setLightCombineMode(Spatial.LightCombineMode.Off);
        setRenderState(ms);
        setRenderState(as);
        StardustController dustController = new StardustController(this, cam);
        this.addController(dustController);
    }
    
    // ensure the viewer is surrounded by stars!
    public void update(Vector3f viewer) {
        // note: funny things happen when scaling things about the origin,
        // so for our purposes we compensate. (we could have used -0.5..0.5)
        // what we want is: -1000..0 -> -1
        // 0..1000 -> 0
        // 1000..2000 -> 1
        int secX = (int) ((viewer.x / blockSize) + ((viewer.x > 0) ? 0 : -1));
        int secY = (int) ((viewer.y / blockSize) + ((viewer.y > 0) ? 0 : -1));
        int secZ = (int) ((viewer.z / blockSize) + ((viewer.z > 0) ? 0 : -1));
        // reduce garbage collection...
        if ((secX != oldSecX) || (secY != oldSecY) || (secZ != oldSecZ)) {
            getLocalTranslation().set(secX * blockSize, secY * blockSize,
                    secZ * blockSize);
            oldSecX = secX;
            oldSecY = secY;
            oldSecZ = secZ;
        }
        updateAlpha(viewer);
    }
    
    public void updateAlpha(Vector3f viewer) {
        // loop through all the points
        for(int k = 0; k < 3; ++k) {
            for(int j = 0; j < 3; ++j) {
                for(int i = 0; i < 3; ++i) {
                    Point p = points[k][j][i];
                    Vector3f pointLoc = p.getLocalTranslation().add(getLocalTranslation());
                    if(pointLoc.distance(viewer) > blockSize * 2) continue;
                    FloatBuffer fb = p.getVertexBuffer();
                    fb.position(0);
                    FloatBuffer c = p.getColorBuffer();
                    c.position(0);
                    // in each point loop through all the stars
                    for(int l = 0; l < numStars; l++) {
                        float xs = fb.get();
                        float ys = fb.get();
                        float zs = fb.get();
                        Vector3f loc = pointLoc.add(xs, ys, zs);
                        float distance = viewer.distance(loc);
                        float alpha = 1f;
                        if(distance > MAX_VIEW_DIST) alpha = 0f;
                        else alpha = distance / MAX_VIEW_DIST;
                        c.put(1f);
                        c.put(1f);
                        c.put(1f);
                        c.put(alpha);
                        p.setColorBuffer(c);
                    }
                    // update the render state of the point.
                    p.updateRenderState();
                }
            }
        }
    }
}